package nio.exercise;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Desc:   用 FileChannel 读取文件中的每一行
 * @author: cww
 * @DateTime: 2020-03-29 22:38
 */

public class ReadLineByChannel {


    public static void main(String[] args) throws IOException {
        RandomAccessFile file = new RandomAccessFile("src/maindemo/java/nio/exercise/split/i.txt","r");
        File file1 = new File("src/maindemo/java/nio/exercise/split/i1.txt");
        RandomAccessFile fileR = new RandomAccessFile(file1,"rw");
        if(!file1.exists()) {
            file1.createNewFile();
        }
        FileChannel rCh = file.getChannel();
        FileChannel wCh = fileR.getChannel();
        ByteBuffer rBuf = ByteBuffer.allocate(1024);
        ByteBuffer wBuf = ByteBuffer.allocate(1024);
        readByLine1(rCh, rBuf, wCh, wBuf, "UTF-8");
    }

    /**
     * FileChannel 按行读取
     * @param inCh
     * @param rbuf
     * @param outCh
     * @param wBuf
     * @param encode
     */
    public static void readByLine(FileChannel inCh, ByteBuffer rbuf, FileChannel outCh,ByteBuffer wBuf,String encode) {
        // 用来缓存上次读取剩下的部分
        try {
            byte[] temp = new byte[0];
            int LF = "\n".getBytes()[0];
            while(inCh.read(rbuf) != -1) {
                // 当前输入流的 position
                int position = rbuf.position();
                // 当前输入流缓冲区
                byte[] rbyte = new byte[position];
                rbuf.flip();
                // 把 buffer 中的内容读入缓冲区
                rbuf.get(rbyte);
                // 判断是否有换行符
                int startNum = 0;
                for(int i =0; i<rbyte.length;i++){
                    if(rbyte[i] == LF){
                        // 存在换行符,新建一个字节数组用来存储 上次剩下的内容加上现在定位到的换行符位置
                        byte[] line = new byte[temp.length + i-startNum +1];
                        // 先将上次读取剩下的行复制到 line 中
                        System.arraycopy(temp, 0,line,0,temp.length);
                        // 再将当前定位到的行复制到 line 中
                        System.arraycopy(rbyte, startNum, line, temp.length, i - startNum +1);
                        // 下一次行读取的起始位置
                        startNum = i +1;
                        // 清空上次读取剩下的缓冲内容
                        temp = new byte[0];
                        // 写入行
                        writeByLine(outCh, wBuf, line, encode);
                    }
                }
                // 检查当前是否还有剩下不完整的一行
                if(startNum < rbyte.length){
                    byte[] line = new byte[temp.length + rbyte.length -startNum];
                    System.arraycopy(temp, 0, line, 0, temp.length);
                    System.arraycopy(rbyte, startNum, line, temp.length, rbyte.length - startNum);
                    // 本次输入缓冲区剩下一个不完整的行，留给下次从通道读入时进行合并
                    temp = line;
                }
                rbuf.clear();
            }
            // 当文件全部读完了，发现最后一行还是不完整
            if(temp.length >0){
                writeByLine(outCh, wBuf, temp, encode);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }

    /**
     * 按行写入文件
     * @param outCh
     * @param wbuf
     * @param line
     * @param encode
     */
    public static void writeByLine(FileChannel outCh, ByteBuffer wbuf, byte[] line, String encode) {
        try {
            wbuf = ByteBuffer.allocate(line.length);
            wbuf.clear();
            wbuf.put(line);
            wbuf.flip();
            while(wbuf.hasRemaining()) {
                outCh.write(wbuf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 按行读取文件
     * @param inCh
     * @param rbuf
     * @param outCh
     * @param wbuf
     * @param encode
     */
    public static void readByLine1(FileChannel inCh, ByteBuffer rbuf, FileChannel outCh, ByteBuffer wbuf, String encode){
        // 存储处理上一次 buf 中剩下的内容
        try {
            byte[] temp = new byte[0];
            int LF = "\n".getBytes()[0];
            while(inCh.read(rbuf) != -1) {
                // 当前 buff 的位置
                int position = rbuf.position();
                byte[] rbyte = new byte[position];
                // 要在翻转之前拿到 position
                rbuf.flip();
                rbuf.get(rbyte);
                // 如果在某个buf 中间有换行符，这个startnum就是换行符所在位置 +1
                int startNum = 0;
                // 遍历检查是否存在换行符
                for(int i=0;i<rbyte.length;i++) {
                    if(LF == rbyte[i]) {
                        // 找到换行符，把 i 之前的内容+temp内容合成一行，
                        byte[] line = new byte[temp.length + i - startNum +1];
                        System.arraycopy(temp, 0,line,0,temp.length);
                        System.arraycopy(rbyte, startNum, line,temp.length, i-startNum+1);
                        startNum = i + 1;
                        temp = new byte[0];
                        writeByLine1(outCh, wbuf, line, encode);
                    }
                }
                // 整个buf 遍历完了，结果 startNum 不是 0，也就是 buf 里剩下一些内容是断行内容
                if(startNum < position) {
                    byte[] temp2 = new byte[temp.length + position - startNum];
                    System.arraycopy(temp, 0, temp2, 0, temp.length);
                    System.arraycopy(rbyte, startNum, temp2, temp.length, position -startNum);
                    temp = temp2;
                }
                rbuf.clear();
            }
            // 如果文件最终有一行不完整行，也进行写入文件
            if(temp.length > 0){
                writeByLine1(outCh, wbuf, temp, encode);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 按行写入文件
     * @param outCh
     * @param outBuf
     * @param line
     * @param encode
     */
    public static void writeByLine1(FileChannel outCh, ByteBuffer outBuf, byte[] line, String encode) {
        try {
            outBuf = ByteBuffer.allocate(line.length);
            outBuf.clear();
            outBuf.put(line);
            outBuf.flip();
            if(outBuf.hasRemaining()) outCh.write(outBuf);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
